﻿using System.Collections;
using System.Collections.Generic;
using Dsl.Ped.Common.Extensions;
using GalaSoft.MvvmLight.Messaging;

namespace Dsl.Ped.Framework.Messages
{
    /// <summary>
    /// This is the implementation of a <see cref="Messenger"/> that is used for the communication
    /// among the editor and add-ins. When the add-ins require to send proper messages which the 
    /// editor listens to, <see cref="FrameworkMessenger"/> should be used to handle this purposes.
    /// </summary>
    public class FrameworkMessenger
    {
        /// <summary>
        /// instance of the <see cref="Messenger"/>
        /// </summary>
        private static readonly FrameworkMessenger instance = new FrameworkMessenger();

        /// <summary>
        /// Hashtable of objects to appropriate <see cref="Messenger"/> instances
        /// </summary>
        private readonly Hashtable object2MessengerHashtable = new Hashtable();

        /// <summary>
        /// List of parents owning the object graphs
        /// </summary>
        private readonly List<object> registeredParents = new List<object>();

        /// <summary>
        /// Returns the <see cref="Messenger"/> for the communication with the editor
        /// </summary>
        /// <param name="element">Object that requires to obtain a messenger. In general, objcts
        /// are provided with the messenger by their membershipt to an object graph. All objects
        /// that are located in the same object graph will be provided with the same <see cref="Messenger"/>
        /// instance</param>
        /// <returns></returns>
        public static Messenger GetMessenger(object element)
        {
            return instance.GetMessengerOfElement(element);
        }

        /// <summary>
        /// Register the parent object of a new object graph. All the object that 
        /// will ask for the <see cref="Messenger"/> and are located in the object
        /// graph belonging to <see cref="parent"/> will be provided with a <see cref="Messenger"/>
        /// registered for the <see cref="parent"/>.
        /// </summary>
        /// <param name="parent"></param>
        public static void RegisterParent(object parent)
        {
            // If the parent is not yet registered
            if (!instance.registeredParents.Contains(parent))
            {
                instance.registeredParents.Add(parent);
            }

            // If the messenger is not registered for the parent, create new Messenger
            // instance for it
            var hash = instance.object2MessengerHashtable[parent];
            if (hash == null)
            {
                instance.object2MessengerHashtable.Add(parent, new Messenger());
            }
        }

        /// <summary>
        /// Get the <see cref="Messenger"/> of a given element.
        /// If the element has already been hashed, use it, otherwise find a messenger
        /// via an object graph and put this relation into <see cref="object2MessengerHashtable"/>.
        /// </summary>
        /// <param name="element">Element which <see cref="Messenger"/> we are looking for</param>
        /// <returns><see cref="Messenger"/> that can be used by the input <see cref="element"/></returns>
        private Messenger GetMessengerOfElement(object element)
        {
            var existingMessenger = this.object2MessengerHashtable[element];
            if (existingMessenger != null) return existingMessenger as Messenger;

            var messenger = this.FindMessenger(element);
            if (messenger != null)
            {
                this.object2MessengerHashtable.Add(element, messenger);
            }

            return messenger;
        }

        /// <summary>
        /// Find a <see cref="Messenger"/> of an <see cref="element"/> by the object graph belonging to
        /// the <see cref="parent"/>
        /// </summary>
        /// <param name="parent">Parent object of the object graph</param>
        /// <param name="element">Element which we are looking for in the object graph</param>
        /// <returns></returns>
        private Messenger FindMessengerByObjectGraph(object parent, object element)
        {
            // Check if the element is in the object graph of 
            // parent
            var isInObjectGraph = parent.IsInObjectGraph(element);
            if (isInObjectGraph)
            {
                // If so, return a Messenger registered for the parent
                return this.object2MessengerHashtable[parent] as Messenger;
            }
            else
            {
                // Otherwise return null, the element doesn't belong to the parent
                // object
                return null;
            }
        }

        /// <summary>
        /// Finds a <see cref="Messenger"/> of the given <see cref="element"/>
        /// </summary>
        /// <param name="element">Element whose messenger we are looking for</param>
        /// <returns>A <see cref="Messenger"/> of the given <see cref="element"/></returns>
        private Messenger FindMessenger(object element)
        {
            foreach (var parent in registeredParents)
            {
                var messenger = this.FindMessengerByObjectGraph(parent, element);
                if (messenger != null) return messenger;
            }

            return null;
        }
    }
}
